{/* Copyright 2024 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License. */}

import {Layout} from '@react-spectrum/docs';
export default Layout;

import docs from 'docs:@react-spectrum/tree';
import treeUtils from 'docs:@react-aria/test-utils/src/tree.ts';
import {HeaderInfo, PropTable, PageDescription, TypeLink, VersionBadge, ClassAPI} from '@react-spectrum/docs';
import {Keyboard} from '@react-spectrum/text';
import packageData from '@react-spectrum/tree/package.json';
import ChevronRight from '@spectrum-icons/workflow/ChevronRight';

```jsx import
import Folder from '@spectrum-icons/workflow/Folder';
import {Flex} from '@react-spectrum/layout';
import FileTxt from '@spectrum-icons/workflow/FileTxt';
import GlobeOutline from '@spectrum-icons/workflow/GlobeOutline';
import Image from '@spectrum-icons/workflow/Image';
import Edit from '@spectrum-icons/workflow/Edit';
import Delete from '@spectrum-icons/workflow/Delete';
import {Text} from '@react-spectrum/text';
import {Collection, TreeView, TreeViewItem, TreeViewItemContent} from '@react-spectrum/tree';
import {JSX} from "react";
import {Key} from "@react-types/shared";
import {ActionGroup, Item} from '@react-spectrum/actiongroup';
import {ActionMenu} from '@react-spectrum/menu';
```

---
category: Collections
keywords: [tree, grid]
---

# TreeView

<PageDescription>{docs.exports.TreeView.description}</PageDescription>

<HeaderInfo
  packageData={packageData}
  componentNames={['TreeView', 'TreeViewItem']}
  sourceData={[
    {type: 'Spectrum', url: 'https://spectrum.adobe.com/page/tree-view/'}
  ]} />

## Example

```tsx example
<TreeView aria-label="Example tree with static contents" defaultExpandedKeys={['documents', 'photos']} height="size-4600" maxWidth="size-6000">
  <TreeViewItem id="documents" textValue="Documents">
    <TreeViewItemContent>
      <Text>Documents</Text>
      <Folder />
    </TreeViewItemContent>
    <TreeViewItem id="project-a" textValue="Project A">
      <TreeViewItemContent>
        <Text>Project A</Text>
        <Folder />
      </TreeViewItemContent>
      <TreeViewItem id="weekly-report" textValue="Weekly-Report">
        <TreeViewItemContent>
          <Text>Weekly Report</Text>
          <FileTxt />
        </TreeViewItemContent>
      </TreeViewItem>
    </TreeViewItem>
    <TreeViewItem id="document-1" textValue="Document 1">
      <TreeViewItemContent>
        <Text>Document 1</Text>
        <FileTxt />
      </TreeViewItemContent>
    </TreeViewItem>
    <TreeViewItem id="document-2" textValue="Document 2">
      <TreeViewItemContent>
        <Text>Document 2</Text>
        <FileTxt />
      </TreeViewItemContent>
    </TreeViewItem>
  </TreeViewItem>
  <TreeViewItem id="photos" textValue="Photos">
    <TreeViewItemContent>
      <Text>Photos</Text>
      <Folder />
    </TreeViewItemContent>
    <TreeViewItem id="image-1" textValue="Image 1">
      <TreeViewItemContent>
        <Text>Image 1</Text>
        <Image />
      </TreeViewItemContent>
    </TreeViewItem>
    <TreeViewItem id="image-2" textValue="Image 2">
      <TreeViewItemContent>
        <Text>Image 2</Text>
        <Image />
      </TreeViewItemContent>
    </TreeViewItem>
    <TreeViewItem id="image-3" textValue="Image 3">
      <TreeViewItemContent>
        <Text>Image 3</Text>
        <Image />
      </TreeViewItemContent>
    </TreeViewItem>
  </TreeViewItem>
</TreeView>
```

## Content
TreeView is a [collection component](collections.html) that provides users with a way to navigate nested hierarchical information.

Basic usage of TreeView, seen in the example above, shows the use of a static collection where the contents of the TreeView are hard coded. Dynamic collections, as shown below, can be used when the options come from an external data source, such as an API, or update over time. Providing the data in this way allows TreeView to automatically cache the rendering of each item, which dramatically improves performance.

Items can be statically defined as children, or generated dynamically using a function based on the data passed to the `items` prop.

Each item has a unique key defined by the data. The `key` of each item element is implicitly defined by the id property of the item object. See [collections](collections.html#unique-keys) to learn more about keys in dynamic collections.

<strong>Note: Asynchronous tree loading (i.e, loading by level or infinite scrolling) is not yet supported in TreeView.</strong>

```tsx example export=true
type MyItem = {
  id: string,
  name: string,
  icon: JSX.Element,
  childItems?: MyItem[]
};

let items: MyItem[] = [
  {id: 'projects', name: 'Projects', icon: <Folder />, childItems: [
    {id: 'project-1', name: 'Project 1', icon: <FileTxt />},
    {id: 'project-2', name: 'Project 2', icon: <Folder />, childItems: [
      {id: 'document-a', name: 'Document A', icon: <FileTxt />},
      {id: 'document-b', name: 'Document B', icon: <FileTxt />},
    ]}
  ]},
  {id: 'reports', name: 'Reports', icon: <Folder />, childItems: [
    {id: 'report-1', name: 'Reports 1', icon: <FileTxt />}
  ]}
];

const DynamicTreeItem = (props) => {
  return (
    <>
      <TreeViewItem id={props.id} textValue={props.name}>
        <TreeViewItemContent>
          <Text>{props.name}</Text>
          {props.icon}
        </TreeViewItemContent>
        <Collection items={props.childItems}>
          {(item: any) => (
            <DynamicTreeItem
              id={item.id}
              icon={item.icon}
              childItems={item.childItems}
              textValue={item.name}
              name={item.name}>
              {item.name}
            </DynamicTreeItem>
          )}
        </Collection>
      </TreeViewItem>
    </>
  );
};

function ExampleTree(props) {
  return (
    <TreeView aria-label="Example tree with dynamic content" height="size-3000" maxWidth="size-6000" items={items} {...props}>
      {(item: MyItem) => (
        <DynamicTreeItem
          id={item.id}
          icon={item.icon}
          childItems={item.childItems}
          textValue={item.name}
          name={item.name} />
      )}
    </TreeView>
  );
}
```

### Internationalization
To internationalize a TreeView, all text content within the TreeView should be localized. This includes the `aria-label` provided to the TreeView if any.
For languages that are read right-to-left (e.g. Hebrew and Arabic), the layout of TreeView is automatically flipped.

## Labeling
### Accessibility
An `aria-label` must be provided to the TreeView for accessibility. If the TreeView is labeled by a separate element, an `aria-labelledby` prop must be provided using the id of the labeling element instead.

## Expansion

By default, TreeView items are initially collapsed. Use `defaultExpandedKeys` to provide a default set of expanded items. Note that the value of the expanded keys must match the `id` prop of the TreeViewItem.

The example below uses `defaultExpandedKeys` to select the items with keys "documents" and "photos".

```tsx example
<ExampleTree
  aria-label="Example tree with default expanded items"
  /*- begin highlight -*/
  defaultExpandedKeys={['projects', 'reports']}
  /*- end highlight -*/
/>
```

### Controlled expansion

To programmatically control item expansion, use the `expandedKeys` prop paired with the `onExpandedChange` callback. The `key` prop from the expanded items will
be passed into the callback when the item is pressed, allowing you to update state accordingly.

Here is how you would control expansion for the above example.

```tsx example
function ControlledExpansion() {
  let [expandedKeys, setExpandedKeys] = React.useState<Set<Key>>(new Set(['projects', 'reports']));

  return (
    <ExampleTree
      aria-label="Example tree with controlled expanded items"
      /*- begin highlight -*/
      expandedKeys={expandedKeys}
      onExpandedChange={setExpandedKeys}
      /*- end highlight -*/
    />
  );
}
```

## Selection

By default, TreeView doesn't allow item selection but this can be enabled using the `selectionMode` prop.
Use `defaultSelectedKeys` to provide a default set of selected items. Note that the value of the selected keys must match the `id` prop of the TreeViewItem.

The example below enables multiple selection mode, and uses `defaultSelectedKeys` to select the items with keys "document-a" and "document-b".

```tsx example
<ExampleTree
  aria-label="Example tree with selection"
  defaultExpandedKeys={['projects', 'project-2']}
  /*- begin highlight -*/
  selectionMode="multiple"
  defaultSelectedKeys={['document-a', 'document-b']}
  /*- end highlight -*/
/>
```

### Controlled selection

To programmatically control item selection, use the `selectedKeys` prop paired with the `onSelectionChange` callback. The `key` prop from the selected items will
be passed into the callback when the item is pressed, allowing you to update state accordingly.

Here is how you would control selection for the above example.

```tsx example
import type {Selection} from '@adobe/react-spectrum';

function ControlledSelection() {
  let [selectedKeys, setSelectedKeys] = React.useState<Selection>(new Set(['document-a', 'document-b']));

  return (
    <ExampleTree
      aria-label="Example tree with controlled selection"
      defaultExpandedKeys={['projects', 'project-2']}
      /*- begin highlight -*/
      selectionMode="multiple"
      selectedKeys={selectedKeys}
      onSelectionChange={setSelectedKeys}
      /*- end highlight -*/
    />
  );
}
```

### Single selection

To limit users to selecting only a single item at a time, `selectionMode` can be set to `single`.

```tsx example
<ExampleTree
  aria-label="Example tree with single selection"
  defaultExpandedKeys={['projects', 'project-2']}
  /*- begin highlight -*/
  selectionMode="single"
  /*- end highlight -*/
/>
```

### Disallow empty selection

TreeView also supports a `disallowEmptySelection` prop which forces the user to have at least one item in the TreeView selected at all times.
In this mode, if a single item is selected and the user presses it, it will not be deselected.

```tsx example
<ExampleTree
  aria-label="Example tree with disallowed empty selection"
  defaultExpandedKeys={['projects', 'project-2']}
  selectionMode="single"
  defaultSelectedKeys={['document-a']}
  /*- begin highlight -*/
  disallowEmptySelection
  /*- end highlight -*/
/>
```

### Disabled items

You can disable specific items by providing an array of keys to TreeView via the `disabledKeys` prop. This will prevent items from being selectable as shown in the example below.

```tsx example
<ExampleTree
  aria-label="Example tree with disabled items"
  defaultExpandedKeys={['projects', 'project-2']}
  selectionMode="single"
  /*- begin highlight -*/
  disabledKeys={['document-a', 'document-b']}
  /*- end highlight -*/
/>
```

### Highlight selection

By default, TreeView uses the checkbox selection style, which includes a checkbox in each item for selection. When the selectionStyle prop is set to `"highlight"`, the checkboxes are hidden,
and the selected items are displayed with a highlighted background instead.

In addition to changing the appearance, the selection behavior also changes depending on the `selectionStyle` prop. In the default checkbox selection style, clicking, tapping, or pressing
the <Keyboard>Space</Keyboard> or <Keyboard>Enter</Keyboard> keys toggles selection for the focused item. Using the arrow keys moves focus but does not change selection.

In the highlight selection style, however, clicking a item with the mouse _replaces_ the selection with only that item. Using the arrow keys moves both focus and selection. To select multiple items,
modifier keys such as <Keyboard>Ctrl</Keyboard>, <Keyboard>Cmd</Keyboard>, and <Keyboard>Shift</Keyboard> can be used. On touch screen devices, selection always behaves as toggle since modifier
keys may not be available.

These selection styles implement the behaviors defined in [Aria Practices](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/#keyboardinteraction).

```tsx example
<ExampleTree
  aria-label="Example tree with highlight selection"
  defaultExpandedKeys={['projects', 'project-2']}
  selectionMode="multiple"
  defaultSelectedKeys={['document-a', 'document-b']}
  /*- begin highlight -*/
  selectionStyle="highlight"
  /*- end highlight -*/
/>
```

## Actions

### Item actions

TreeView supports item actions via the `onAction` prop, which is useful for functionality such as navigation. When nothing is selected, the TreeView performs actions by default when clicking or tapping a item.
Items may be selected using the checkbox, or by long pressing on touch devices. When at least one item is selected, the TreeView is in selection mode,
and clicking or tapping a item toggles the selection. Actions may also be triggered via the <Keyboard>Enter</Keyboard> key, and selection using the <Keyboard>Space</Keyboard> key.

This behavior is slightly different in the highlight selection style, where single clicking selects the item and actions are performed via double click. Touch and keyboard behaviors are unaffected.

```tsx example
<Flex direction="column" gap="size-300">
  <ExampleTree
    aria-label="Example tree with item actions and checkbox selection"
    defaultExpandedKeys={['projects', 'project-2']}
    /*- begin highlight -*/
    selectionMode="multiple"
    onAction={key => alert(`Opening item ${key}...`)}
    /*- end highlight -*/
  />
  <ExampleTree
    aria-label="Example tree with item actions and highlight selection"
    defaultExpandedKeys={['projects', 'project-2']}
    /*- begin highlight -*/
    selectionMode="multiple"
    selectionStyle="highlight"
    onAction={key => alert(`Opening item ${key}...`)}
    /*- end highlight -*/
  />
</Flex>
```

### Links

Tree items may also be links to another page or website. This can be achieved by passing the `href` prop to the `<TreeViewItem>` component. Links behave the same way as described above for item actions depending on the `selectionMode` and `selectionStyle`.

```tsx example
<TreeView aria-label="Example tree with links" defaultExpandedKeys={new Set(['bookmarks'])} height="size-2000" maxWidth="size-6000">
  <TreeViewItem id="bookmarks" textValue="Bookmarks">
    <TreeViewItemContent>
      <Text>Bookmarks</Text>
      <Folder />
    </TreeViewItemContent>
    <TreeViewItem href="https://adobe.com/" target="_blank" id="adobe" textValue="Adobe">
      <TreeViewItemContent>
        <Text>Adobe</Text>
        <GlobeOutline />
      </TreeViewItemContent>
    </TreeViewItem>
    <TreeViewItem href="https://google.com/" target="_blank" id="google" textValue="Google">
      <TreeViewItemContent>
        <Text>Google</Text>
        <GlobeOutline />
      </TreeViewItemContent>
    </TreeViewItem>
    <TreeViewItem href="https://nytimes.com/" target="_blank" id="nytimes" textValue="New York Times">
      <TreeViewItemContent>
        <Text>New York Times</Text>
        <GlobeOutline />
      </TreeViewItemContent>
    </TreeViewItem>
  </TreeViewItem>
</TreeView>
```

#### Client side routing

The `<TreeViewItem>` component works with frameworks and client side routers like [Next.js](https://nextjs.org/) and [React Router](https://reactrouter.com/en/main). As with other React Spectrum components that support links, this works via the [Provider](Provider.html) component at the root of your app. See the [client side routing guide](routing.html) to learn how to set this up.

### With action groups

```tsx example
<TreeView aria-label="Example tree with action groups" height="size-3000" maxWidth="size-6000" items={items}>
  {function renderItem(item: MyItem) {
    return (
      <TreeViewItem textValue={item.name}>
        <TreeViewItemContent>
          <Text>{item.name}</Text>
          {item.icon}
          <ActionGroup onAction={(key) => alert(`Item: ${item.id}, Action: ${key}`)}>
            <Item key="edit" textValue="Edit">
              <Edit />
              <Text>Edit</Text>
            </Item>
            <Item key="delete" textValue="Delete">
              <Delete />
              <Text>Delete</Text>
            </Item>
          </ActionGroup>
        </TreeViewItemContent>
        <Collection items={item.childItems}>
          {renderItem}
        </Collection>
      </TreeViewItem>
    )
  }}
</TreeView>
```

### With action menus

```tsx example
import {Collection} from '@adobe/react-spectrum';

<TreeView aria-label="Example tree with action menus" height="size-3000" maxWidth="size-6000" items={items}>
  {function renderItem(item: MyItem) {
    return (
      <TreeViewItem textValue={item.name}>
        <TreeViewItemContent>
          <Text>{item.name}</Text>
          {item.icon}
          <ActionMenu onAction={(key) => alert(`Item: ${item.id}, Action: ${key}`)}>
            <Item key="edit" textValue="Edit">
              <Edit />
              <Text>Edit</Text>
            </Item>
            <Item key="delete" textValue="Delete">
              <Delete />
              <Text>Delete</Text>
            </Item>
          </ActionMenu>
        </TreeViewItemContent>
        <Collection items={item.childItems}>
          {renderItem}
        </Collection>
      </TreeViewItem>
    )
  }}
</TreeView>
```

## Props

### TreeView props

<PropTable component={docs.exports.TreeView} links={docs.links} style={{marginBottom: '40px'}} />

### TreeViewItemContent props

<PropTable component={docs.exports.TreeViewItemContent} links={docs.links} style={{marginBottom: '40px'}} />

### TreeViewItem props

<PropTable component={docs.exports.TreeViewItem} links={docs.links} style={{marginBottom: '40px'}} />

## Visual options
### Empty state
Use the `renderEmptyState` prop to customize what the TreeView will display if there are no items provided.

```tsx example
import {Content} from '@react-spectrum/view';
import {IllustratedMessage} from '@react-spectrum/illustratedmessage';
import NotFound from '@spectrum-icons/illustrations/NotFound';
import {Heading} from '@react-spectrum/text';

function renderEmptyState() {
  return (
    <IllustratedMessage>
      <NotFound />
      <Heading>No results</Heading>
      <Content>No results found</Content>
    </IllustratedMessage>
  );
}

<TreeView aria-label="Example tree for empty state" height="size-2400" maxWidth="size-6000" renderEmptyState={renderEmptyState}>
  {[]}
</TreeView>
```

## Testing

The TreeView features long press interactions on its items depending on the item actions provided and if user is interacting with the list on
a touch device. Please see the following sections in the testing docs for more information on how to handle these
behaviors in your test suite.

[Timers](./testing.html#timers)

[Desktop vs Mobile](./testing.html#desktop-vs-mobile)

[Long press](./testing.html#simulating-user-long-press)

Please also refer to [React Spectrum's test suite](https://github.com/adobe/react-spectrum/blob/main/packages/%40react-spectrum/tree/test/TreeView.test.tsx) if you find that the above
isn't sufficient when resolving issues in your own test cases.

### Test utils <VersionBadge version="beta" style={{marginLeft: 4, verticalAlign: 'bottom'}} />

`@react-spectrum/test-utils` offers common tree interaction utilities which you may find helpful when writing tests. See [here](./testing.html#react-spectrum-test-utils) for more information on how to setup these utilities
in your tests. Below is the full definition of the tree tester and a sample of how you could use it in your test suite.

```ts
// Tree.test.ts
import {render, within} from '@testing-library/react';
import {theme} from '@react-spectrum/theme-default';
import {User} from '@react-spectrum/test-utils';

let testUtilUser = new User({interactionType: 'mouse'});
// Other setup, be sure to check out the suggested mocks mentioned above in https://react-spectrum.adobe.com/react-spectrum/TreeView.html#testing

it('TreeView can select a row via keyboard', async function () {
  // Render your test component/app and initialize the Tree tester
  let {getByTestId} = render(
    <Provider theme={defaultTheme}>
      <TreeView data-testid="test-tree" selectionMode="multiple">
        ...
      </TreeView>
    </Provider>
  );
  let treeTester = testUtilUser.createTester('Tree', {root: getByTestId('test-tree'), interactionType: 'keyboard'});

  await treeTester.toggleRowSelection({row: 0});
  expect(treeTester.selectedRows).toHaveLength(1);
  expect(within(treeTester.rows[0]).getByRole('checkbox')).toBeChecked();

  await treeTester.toggleRowSelection({row: 1});
  expect(treeTester.selectedRows).toHaveLength(2);
  expect(within(treeTester.rows[1]).getByRole('checkbox')).toBeChecked();

  await treeTester.toggleRowSelection({row: 0});
  expect(treeTester.selectedRows).toHaveLength(1);
  expect(within(treeTester.rows[0]).getByRole('checkbox')).not.toBeChecked();
});
```

<ClassAPI links={treeUtils.links} class={treeUtils.exports.TreeTester} />
